/**
 * 
 */
package xj.toolkit.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;

/**
 * @author hp
 *
 */
public class Functor implements ClosureEx {

	private static final Logger logger = LoggerFactory.getLogger(Functor.class);

	private boolean canceled = false;
	private Object target = null;
	private List<Method> methods = Lists.newArrayList();

	public Functor(Object target, String methodName) {
		this.target = target;
		if (null == this.target) {
			throw new RuntimeException(" target is null.");
		}

		Method[] methods = null;
		Class<?> itr = target.getClass();
		while (!itr.equals(Object.class)) {
			methods = (Method[]) ArrayUtils.addAll(itr.getDeclaredMethods(), methods);
			itr = itr.getSuperclass();
		}
		for (Method methodItr : methods) {
			if (methodItr.getName().equals(methodName)) {
				methodItr.setAccessible(true);
				this.methods.add(methodItr);
			}
		}
		if (null == this.methods || 0 == this.methods.size()) {
			throw new RuntimeException("method [" + target.getClass() + "." + methodName + "] !NOT! exist.");
		}
	}

	public void execute(Object... args) {
		if (canceled) {
			return;
		}

		Method matchMethod = null;

		for (Method method : methods) {
			if (method.getParameterCount() != args.length) {
				continue;
			}

			Class<?>[] paramTypes = method.getParameterTypes();
			boolean paramTypeMatch = false;
			for (int i = 0; i < args.length; i++) {
				Class<?> argClass = args[i].getClass();
				if (argClass.getName().equals(paramTypes[i].getName())) {
					paramTypeMatch = true;
					continue;
				}
				
				if (ClassUtils.isAssignable(args[i].getClass(), paramTypes[i], true)) {
					paramTypeMatch = true;
				}
			}

			if (paramTypeMatch) {
				matchMethod = method;
			}
		}

		if (matchMethod == null) {
			throw new RuntimeException("Cannot found suitable method. args[" + ArrayUtils.toString(args) + "].");
		}

		try {
			matchMethod.invoke(this.target, args);
		} catch (IllegalArgumentException e) {
			logger.error("execute, target[" + this.target + "], method[" + matchMethod.getName() + "], args["
					+ ToStringBuilder.reflectionToString(args) + "].", e);
		} catch (IllegalAccessException e) {
			logger.error("execute, target[" + this.target + "], method[" + matchMethod.getName() + "], args["
					+ ToStringBuilder.reflectionToString(args) + "].", e);
		} catch (InvocationTargetException e) {
			logger.error("execute, target[" + this.target + "], method[" + matchMethod.getName() + "], args["
					+ ToStringBuilder.reflectionToString(args) + "].", e);
		}

	}

	/**
	 * @return the canceled
	 */
	public boolean isCanceled() {
		return canceled;
	}

	/**
	 * @param canceled
	 *            the canceled to set
	 */
	public void setCanceled(boolean canceled) {
		this.canceled = canceled;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		sb.append(this.target);
		sb.append(".");
		sb.append(this.methods == null || this.methods.size() == 0 ? "method:null" : this.methods.get(0).getName());

		if (this.canceled) {
			sb.append("[canceled]");
		}
		return sb.toString();
	}
}
